// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu

/**
OrderLatency Model (read only, no train)
**/

#pragma once

#include <algorithm>
#include <experimental/filesystem>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>

#include <highfive/H5DataSet.hpp>
#include <highfive/H5DataSpace.hpp>
#include <highfive/H5File.hpp>
#include <span.hpp>

#include "coin2/exchange/base/feed/update.h"
#include "coin2/strategy/strategy.h"
#include "coin2/support/order_latency/feature.h"

using coin2::exchange::base::feed::FeedUpdate;

enum Y {
  SUBMIT_TO_CONFIRM,
  SUBMIT_TO_FIRST_FILL,
  CANCEL_SUBMIT_TO_CONFIRM,
  //
  NUM_Y
};

class SimpleLinearModel {
 public:
  explicit SimpleLinearModel(const std::experimental::filesystem::path& path) {
    HighFive::File file(path, HighFive::File::ReadOnly);

    {
      HighFive::DataSet ds = file.getDataSet("weights");
      CHECK_THROW(ds.getSpace().getNumberDimensions() == 1);
      weights_.resize(ds.getElementCount());
      ds.read(weights_.data());
    }
    {
      HighFive::DataSet ds = file.getDataSet("bias");
      CHECK_THROW(ds.getSpace().getNumberDimensions() == 0);
      CHECK_THROW(ds.getElementCount() == 1);
      ds.read(&bias_);
    }
  }

  double Predict(nonstd::span<double> features) {
    CHECK_THROW(features.size() == weights_.size());
    double sum = bias_;
    for (int i = 0; i < Feature::NUM_FEATURE; i++) {
      sum += features[i] * weights_[i];
    }
    return sum;
  }

 private:
  std::vector<double> weights_{};
  double bias_{};
};

class OrderLatencyModel : public coin2::strategy::IStrategy {
 public:
  explicit OrderLatencyModel(
      const MarketExchangeApi& mea,
      const std::string& absolute_norm,
      const std::string& relative_norm)
      : target_absolute_norm_(absolute_norm), features_(absolute_norm) {
    {
      auto path = fmt::format(
          "../coin_binary/latency-model/order-submit-to-accepted/{}:{}.h5",
          mea.String(),
          relative_norm);
      if (std::experimental::filesystem::exists(path)) {
        order_submit_to_accepted_.reset(new SimpleLinearModel(path));
      }
    }
    {
      auto path = fmt::format(
          "../coin_binary/latency-model/order-submit-to-first-fill/{}:{}.h5",
          mea.String(),
          relative_norm);
      if (std::experimental::filesystem::exists(path)) {
        order_submit_to_first_fill_.reset(new SimpleLinearModel(path));
      }
    }
    {
      auto path = fmt::format(
          "../coin_binary/latency-model/cancel-submit-to-confirmed/{}:{}.h5",
          mea.String(),
          relative_norm);
      if (std::experimental::filesystem::exists(path)) {
        cancel_submit_to_confirmed_.reset(new SimpleLinearModel(path));
      }
    }
  }

  void onTradeFeed(const FeedUpdate& upd) override {
    if (upd.product().absolute_norm() != target_absolute_norm_) {
      return;
    }
    are_features_outdated_ = true;
    features_.onTradeFeed(upd);
  }

  // return nanoseconds
  int64_t PredictSubmitToAccepted() {  // NOLINT
    MakeSureFeaturesArePrepared();
    auto y = static_cast<int64_t>(order_submit_to_accepted_->Predict(x_));
    return std::max(int64_t(0), y);
  }

  // return nanoseconds
  int64_t PredictSubmitToFirstFill() {  // NOLINT
    MakeSureFeaturesArePrepared();
    int64_t y = static_cast<int64_t>(order_submit_to_first_fill_->Predict(x_));
    return std::max(int64_t(0), y);
  }

  // return nanoseconds
  int64_t PredictCancelSubmitToConfirm() {
    MakeSureFeaturesArePrepared();
    auto y = static_cast<int64_t>(cancel_submit_to_confirmed_->Predict(x_));
    return std::max(int64_t(0), y);
  }

  bool has_order_submit_to_accepted() { return order_submit_to_accepted_ != nullptr; }
  bool has_order_submit_to_first_fill() { return order_submit_to_first_fill_ != nullptr; }
  bool has_cancel_submit_to_confirmed() { return cancel_submit_to_confirmed_ != nullptr; }

 private:
  void MakeSureFeaturesArePrepared() {
    if (are_features_outdated_) {
      features_.GetX(x_);
      are_features_outdated_ = false;
    }
  }

  std::string target_absolute_norm_;
  std::array<double, Feature::NUM_FEATURE> x_{};
  OrderLatencyFeatures features_;
  bool are_features_outdated_ = true;
  std::unique_ptr<SimpleLinearModel> order_submit_to_accepted_;
  std::unique_ptr<SimpleLinearModel> order_submit_to_first_fill_;
  std::unique_ptr<SimpleLinearModel> cancel_submit_to_confirmed_;
};
